/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets.rating;

import com.codemagi.login.AccessException;
import com.codemagi.login.LoginException;
import com.codemagi.servlets.*;
import com.codemagi.servlets.rating.model.*;
import com.codemagi.servlets.validation.*;
import com.codemagi.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.QueryResults;

/**
 * Class extending the <code>HttpServlet</code> to implement
 * the controller for ratings.
 * <P>
 * The following actions are supported: rate
 *
 * @author August Detlefsen for CodeMagi, Inc.
 * @version 1.0 12/05/2008
 */
public class RatingController extends com.codemagi.servlets.BaseServlet {

    //set to true to view debug output
    Logger log = Logger.getLogger(this.getClass());

    //The string name for the Voter Cookie
    public static final String VOTER_COOKIE_NAME = "votercookie";

    /**
     * Initializes the servlet when it is loaded by the
     * servlet engine.
     *
     * @param config the configuration as <code>ServletConfig</code>
     *
     * @throws ServletException if initialization fails.
     */
    public void init(ServletConfig config)
	throws ServletException {

	super.init(config);

        //create validators
        IValidator integerValidator = new IntegerValidator();
        IValidator notNullValidator = new NotNullValidator();
        IValidator[] int_notNull_validators =
            { integerValidator, notNullValidator };

        //add input validators
        addValidator("rate", "parentId", int_notNull_validators);
        addValidator("rate", "rate", int_notNull_validators);
    }


    /**
     * Handles the incoming requests.
     * <p>
     * This implementation ensures authenticated session
     * existence, retrieves the <code>acton</code>
     * and <code>todo</code> parameters, and dispatches all
     * valid actions to the target dispatchers.
     * <p>
     * The flow of the process is described in the related
     * documentation.
     * <p>
     * Application related errors/exceptions are handled
     * by forwarding the request to an error page, or the actual page
     * in case of an inlined error.
     *
     * @param request   A reference to the actual <code>HttpServletRequest</code> instance.
     * @param response  A reference to the actual <code>HttpServletResponse</code> instance.
     *
     * @throws ServletException if servlet related operations fail.
     * @throws IOException if i/o operations fail.
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
	
	//next page to forward to
	String nextPage = VIEW_MAIN;

        //get the command or acton passed in from the URL                              
        String command      = request.getPathInfo();
	String action       = request.getParameter("acton");

	log.debug("COMMAND: " + command);
	log.debug("ACTON:   " + action);

	try {	
	    if ("rate".equals(action)) {
                nextPage = dispatchRate(request, response);
		
            }

	} catch (AccessException ae) {
	    log.debug("", ae);
	    nextPage = VIEW_ACCESS_DENIED;
	    
	} catch (LoginException le) {
	    nextPage = VIEW_LOGIN;

	}
		
	//dispatch back to where it came from
	log.debug("Acton complete, forarding to: " + nextPage);
	if ( !VIEW_NONE.equals(nextPage) ) {
	    RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(nextPage);
	    dispatcher.forward(request, response);
	}

	return;
    }


    /**
     * Dispatches <code>rate</code> actions. Adds a rating to the specified parent object
     */
    protected String dispatchRate(HttpServletRequest request, HttpServletResponse response) 
	throws LoginException, AccessException {

	//name variables that need to be available in full method scope
	Database db      = null;

	try {
	    log.debug("about to process request parameters");

	    //get 2nd request params
            Integer parentId    = getInteger("parentId", request);
            log.debug("parentId="+parentId);

	    //get 1st request params
	    Integer rate        = getInteger("rate", request);
	    log.debug("rate="+rate);
	    
            //did we get any validation errors?
            checkValidation(request);

	    //do the DB transaction
            db = getJDODatabase();
            db.begin();

	    //does the user have a voting cookie
	    log.debug("does the user have a voting cookie");
	    Cookie votingCookie = getCookie(request, VOTER_COOKIE_NAME);

	    //get the cookie ID
	    String cookieId = null;
	    
	    //if cookie is null, create a new cookie and persist new RatingUser
	    log.debug("votingCookie = " + votingCookie);
	    if (votingCookie == null) {
		log.debug("votingCookie == null");
		
		//create unique id for this new user
		cookieId = UIDGenerator.getUID();
		log.debug("cookieId = " + cookieId);

		//set cookie
		javax.servlet.http.Cookie voterCookie = new javax.servlet.http.Cookie(VOTER_COOKIE_NAME, cookieId);
		voterCookie.setMaxAge(Integer.MAX_VALUE);    //cookie will expire in ~68 years
		    
		//add cookie
		response.addCookie(voterCookie);

	    } else {
		// cookie is NOT null, so get from castor
		log.debug("about to getValue from cookie");
		cookieId = votingCookie.getValue();
                log.debug("cookieId = " + cookieId);

	    }

	    log.debug("oql, take 2");
	    //has this user submitted a rating for ths object before? 
	    OQLQuery oql = db.getOQLQuery(" SELECT u FROM com.codemagi.servlets.rating.model.Rating u " + 
					  " WHERE u.nodeId = $1 " +
					  " AND u.cookieId = $2 ");
	    oql.bind(parentId);
	    oql.bind(cookieId);

	    QueryResults results = oql.execute();
	    if ( results.hasMore() ) {
		log.debug("ALREADY VOTED!");

                //get ready for a new rating
                Rating rating = (Rating)results.next();

                //set new vote in bean
                rating.setRating(rate);

	    } else {
		//get ready for a new rating
		Rating rating = new Rating();

		//set values in bean
		rating.setRating(rate);
		rating.setNodeId(parentId);
		rating.setCookieId(cookieId);
		
		//save the rating in the db
		db.create(rating);	    
	    }

	    db.commit();
		
	    //clear the cache for this rating
	    org.exolab.castor.jdo.CacheManager cm = db.getCacheManager();
	    cm.expireCache(com.codemagi.servlets.rating.model.AverageRating.class, parentId);
	    log.debug("cache cleansing complete");
		
	    //load the average rating for this node
	    AverageRating averageParent = getAverageRating(parentId);

	    //create simple comma delimited string to return to client via ajax
            String outputString = averageParent.getVotes() + "," + averageParent.getAverage() + "," + averageParent.getId();
            log.debug("Sending back to client: " + outputString);

	    //send and close the stream
	    marshal(outputString, response);
	    
	} catch (AppException sne) {
	    log.debug("", sne);

	    request.setAttribute("error_message", sne.getMessage());

	} catch (PersistenceException e) {
	    log.debug("", e);
	    
	    request.setAttribute("error_message", MSG_DATABASE_ERROR);

	} finally {
	    closeJDODatabase(db);
        }

	return VIEW_NONE;
    }
    

    //UTILITY METHODS
    
    /**                                                                                                               
     * Returns the total rating for a parent
     *                                                                                                               
     * @param db         The already opened database instance                                                           
     * @param parentId   ID of the parent the rating applies to
     */
    protected AverageRating getAverageRating(Database db, Integer parentId)
        throws PersistenceException {

	//sanity check
	if (parentId == null) return new AverageRating();

	AverageRating output = (AverageRating)db.load(AverageRating.class, parentId, Database.ReadOnly);
 
	if (output == null) output = new AverageRating();

	return output;
    }

    
    /**                                                                                                                        
     * Returns the total rating for a parent                                                                              
     *                                                                                                               
     * @param parentId   ID of the parent the rating applies to                                              
     */
    protected AverageRating getAverageRating(Integer parentId) {

	if (parentId == null) return new AverageRating();

	AverageRating output = null;

	Database db          = null;
	
	try {
	    //do the DB transaction                                                                        
            db = getJDODatabase();
            db.begin();

	    output = getAverageRating(db, parentId);

	} catch (PersistenceException pe) {
	    log.debug("", pe);

	} finally {
	    closeJDODatabase(db);
	}

	if (output == null) output = new AverageRating();

	return output;
    }

}
